// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package command

import (
	"fmt"
	"strings"

	"github.com/hashicorp/cli"
	"github.com/hashicorp/nomad/api/contexts"
	"github.com/posener/complete"
)

type StatusCommand struct {
	Meta

	// Placeholder bool to allow passing of verbose flags to subcommands.
	verbose bool
	openURL bool
}

func (c *StatusCommand) Help() string {
	helpText := `
Usage: nomad status [options] <identifier>

  Display the status output for any given resource. The command will
  detect the type of resource being queried and display the appropriate
  status output.

  If no arguments are provided, the command will fallback to "nomad job status",
  which will list all jobs.

General Options:

  ` + generalOptionsUsage(usageOptsDefault) + `

Status Options:

  -verbose
    Display full information.

  -ui
    Open the status page in the browser.
`

	return strings.TrimSpace(helpText)
}

func (c *StatusCommand) Synopsis() string {
	return "Display the status output for a resource"
}

func (c *StatusCommand) AutocompleteFlags() complete.Flags {
	return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
		complete.Flags{
			"-verbose": complete.PredictNothing,
			"-ui":      complete.PredictNothing,
		})
}

func (c *StatusCommand) AutocompleteArgs() complete.Predictor {
	return complete.PredictFunc(func(a complete.Args) []string {
		client, err := c.Meta.Client()
		if err != nil {
			return nil
		}

		resp, _, err := client.Search().PrefixSearch(a.Last, contexts.All, nil)
		if err != nil {
			return []string{}
		}

		final := make([]string, 0)

		for _, matches := range resp.Matches {
			if len(matches) == 0 {
				continue
			}

			final = append(final, matches...)
		}

		return final
	})
}

func (c *StatusCommand) Run(args []string) int {
	flags := c.Meta.FlagSet("status", FlagSetClient)
	flags.Usage = func() { c.Ui.Output(c.Help()) }
	flags.BoolVar(&c.verbose, "verbose", false, "")
	flags.BoolVar(&c.openURL, "ui", false, "")
	if err := flags.Parse(args); err != nil {
		c.Ui.Error(fmt.Sprintf("Error parsing arguments: %q", err))
		return 1
	}

	// Store the original arguments so we can pass them to the routed command
	argsCopy := args

	// Check that we got exactly one evaluation ID
	args = flags.Args()

	// Get the HTTP client
	client, err := c.Meta.Client()
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error initializing client: %q", err))
		return 1
	}

	// If no identifier is provided, default to listing jobs
	if len(args) == 0 {
		cmd := &JobStatusCommand{Meta: c.Meta}
		return cmd.Run(argsCopy)
	}

	id := args[len(args)-1]

	// Query for the context associated with the id
	res, _, err := client.Search().PrefixSearch(id, contexts.All, nil)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error querying search with id: %q", err))
		return 1
	}

	if res.Matches == nil {
		c.Ui.Error(fmt.Sprintf("No matches returned for query: %q", err))
		return 1
	}

	var match contexts.Context
	exactMatches := 0
	for ctx, vers := range res.Matches {
		if len(vers) > 0 && vers[0] == id {
			match = ctx
			exactMatches++
		}
	}

	if exactMatches > 1 {
		c.logMultiMatchError(id, res.Matches)
		return 1
	} else if exactMatches == 0 {
		matchCount := 0
		for ctx, vers := range res.Matches {
			l := len(vers)
			if l == 1 {
				match = ctx
				matchCount++
			}

			// Only a single result should return, as this is a match against a full id
			if matchCount > 1 || l > 1 {
				c.logMultiMatchError(id, res.Matches)
				return 1
			}
		}
	}

	var cmd cli.Command
	switch match {
	case contexts.Evals:
		cmd = &EvalStatusCommand{Meta: c.Meta}
	case contexts.Nodes:
		cmd = &NodeStatusCommand{Meta: c.Meta}
	case contexts.Allocs:
		cmd = &AllocStatusCommand{Meta: c.Meta}
	case contexts.Jobs:
		cmd = &JobStatusCommand{Meta: c.Meta}
	case contexts.Deployments:
		cmd = &DeploymentStatusCommand{Meta: c.Meta}
	case contexts.Namespaces:
		cmd = &NamespaceStatusCommand{Meta: c.Meta}
	case contexts.Quotas:
		cmd = &QuotaStatusCommand{Meta: c.Meta}
	case contexts.Plugins:
		cmd = &PluginStatusCommand{Meta: c.Meta}
	case contexts.Volumes:
		cmd = &VolumeStatusCommand{Meta: c.Meta}
	default:
		c.Ui.Error(fmt.Sprintf("Unable to resolve ID: %q", id))
		return 1
	}

	return cmd.Run(argsCopy)
}

// logMultiMatchError is used to log an error message when multiple matches are
// found. The error message logged displays the matched IDs per context.
func (c *StatusCommand) logMultiMatchError(id string, matches map[contexts.Context][]string) {
	c.Ui.Error(fmt.Sprintf("Multiple matches found for id %q", id))
	for ctx, vers := range matches {
		if len(vers) == 0 {
			continue
		}

		c.Ui.Error(fmt.Sprintf("\n%s:", strings.Title(string(ctx))))
		c.Ui.Error(strings.Join(vers, ", "))
	}
}
